package logic;

import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;



/**
 * A simple predator/prey simulator, based on a rectangular field
 * containing rabbits and foxes.
 * 
 * @author David J. Barnes and Michael Kolling
 * @version 2008.03.30
 */
public class Simulator extends AbstractModel implements Runnable
{
    // Constants representing configuration information for the simulation.
    // The default width for the grid.
    private static final int DEFAULT_WIDTH = 50;
    // The default depth of the grid.
    private static final int DEFAULT_DEPTH = 50;
    // The probability that a fox will be created in any given grid position.
    private static double FOX_CREATION_PROBABILITY = 0.02;
    // The probability that a rabbit will be created in any given grid position.
    private static double RABBIT_CREATION_PROBABILITY = 0.08;    
    //The probability that a duck will be created in any given grid position.
    private static double DUCK_CREATION_PROBABILITY = 0.03;
    //The probability that a hunter will be created in any given grid position.
    private static double HUNTER_CREATION_PROBABILITY = 0.001;
    //The probability that a patch of grass will be created in any given grid position.
    private static double GRASS_CREATION_PROBABILITY = 0.3;
    // List of animals in the field.
    private List<Actor> actors;
    // The current state of the field.
    private Field field;
    // The current step of the simulation.
    private int step;
    // A graphical view of the simulation.
    private boolean run;
    // the number of steps wich will be simulated
	private int numOfSteps;
	// gives the current field
	private FieldStats stats;
	// the width of the current field
    private int width;
    // the depth of the current field
    private int depth;
    // a list of the newly born actors
    private List<Actor> newActors;
    //speed of each step in MS
    private int stepSpeed= 50;
	
	
    /**
     * Construct a simulation field with default size.
     */
    public Simulator()
    {
        this(DEFAULT_DEPTH, DEFAULT_WIDTH);
    }
    
    /**
     * Create a simulation field with the given size.
     * @param depth Depth of the field. Must be greater than zero.
     * @param width Width of the field. Must be greater than zero.
     */
    public Simulator(int depth, int width)
    {
        if(width <= 0 || depth <= 0) {
            System.out.println("The dimensions must be greater than zero.");
            System.out.println("Using default values.");
            depth = DEFAULT_DEPTH;
            width = DEFAULT_WIDTH;
        }
        this.width=width;
        this.depth=depth;
        
        actors = new ArrayList<Actor>();
        field = new Field(depth, width);
        stats = new FieldStats();
    }
    
    /**
     * the width of the field times the depth of the field
     * @return the gridsize of the current field
     */
    public int getGridSize() {
    	return width*depth;
    }
    
    /**
     * Run the simulation from its current state for a reasonably long period,
     * e.g. 500 steps.
     */
    public void runLongSimulation()
    {
        simulate(500);
    }
    
    /**
     * Run the simulation from its current state for the given number of steps.
     * Stop before the given number of steps if it ceases to be viable.
     * @param numSteps The number of steps to run for.
     */
    public void simulate(int numSteps)
    {
    	this.numOfSteps=numSteps;
        run=true;
        new Thread(this).start();
    }
    
    /**
     * Run the simulation from its current state for a single step.
     * Iterate over the whole field updating the state of each
     * fox and rabbit.
     */
    public void simulateOneStep()
    {
        step++;

        // Provide space for newborn animals.
        newActors = new ArrayList<Actor>();        
        // Let all rabbits act.
        for(Iterator<Actor> it = actors.iterator(); it.hasNext(); ) {
        	Actor actors = it.next();
        	actors.act(newActors);
            if(! actors.isActive()) {
                it.remove();
            }
        }
               
        // Add the newly born foxes and rabbits to the main lists.
        actors.addAll(newActors);
        if(!stats.isViable(field)){
        	run=false;
        }
    }
 
    /**
     * Reset the simulation to a starting position.
     */
    public void reset()
    {
        step = 0;
        actors.clear();
        populate();
        
        // Show the starting state in the view.
        notifyViews();
    }
    
    /**
     * Randomly populate the field with foxes and rabbits.
     */
    private void populate()
    {
    	Random rand = Randomizer.getRandom();
    	field.clear();
        for(int row = 0; row < field.getDepth(); row++) {
            for(int col = 0; col < field.getWidth(); col++) {
            	double value = rand.nextDouble();
                if(value <= HUNTER_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    Hunter hunter = new Hunter(field, location);
                    actors.add(hunter);
                }
                else if(value <= FOX_CREATION_PROBABILITY+HUNTER_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    Fox fox = new Fox(true, field, location);
                    actors.add(fox);
                }
                else if(value <= DUCK_CREATION_PROBABILITY+FOX_CREATION_PROBABILITY+HUNTER_CREATION_PROBABILITY) {
                	Location location = new Location(row, col);
                	Duck duck = new Duck(true, field, location);
                	actors.add(duck);
                }
                else if(value <= RABBIT_CREATION_PROBABILITY+DUCK_CREATION_PROBABILITY+FOX_CREATION_PROBABILITY+HUNTER_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    Rabbit rabbit = new Rabbit(true, field, location);
                    actors.add(rabbit);
                }
              else if(value <= GRASS_CREATION_PROBABILITY+RABBIT_CREATION_PROBABILITY+DUCK_CREATION_PROBABILITY+FOX_CREATION_PROBABILITY+HUNTER_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    Grass grass = new Grass(true, field, location);
                   actors.add(grass);
               }

                // else leave the location empty.
            }
        }
    }
	@Override
	public void run() {
		for(int i=0;i<numOfSteps && run;i++) {
			simulateOneStep();
			notifyViews();
			try {
				Thread.sleep(stepSpeed);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		run=false;
	}
	
	/**
	 * gives the list of the newly born
	 * @return a list with the newly born
	 */
	public List<Actor> getList()
	{
		return actors;
	}

	/**
	 * gives the current step the simulation is in
	 * @return the current step the simulation is in
	 */
	public int getStep() {
		return step;
	}
	
	/** 
	 * gives the current stats of the field
	 * @return the current stats of the field
	 */
	public FieldStats getStats() {
		return stats;
	}

	/**
	 * gives the current field
	 * @return the current field
	 */
	public Field getField() {
		return field;
	}
	
	/**
	 * gives the depth of the current field
	 * @return the depth of the current field
	 */
	public int getDepth(){
		return depth;
	}
	
	/**
	 * gives the width of the current field
	 * @return the width of the current field
	 */
	public int getWidth(){
		return width;
	}
	
	/**
	 * gives the list of the newly born actors 
	 * @return the list of the newly born actors
	 */
	public List<Actor> getNewActors()
	{
		return newActors;
	}
	
	/**
	 * sets the current field to new width and depth
	 * @param width
	 * @param depth
	 */
	public void setField(int width, int depth)
	{
		 this.width=width;
	     this.depth=depth;
	     field = new Field(depth,width);
	     reset();
	}
	 
	/**
	 * sets the current stepSpeed to the new speed
	 * @param speed
	 */
	public void setStepSpeed(int speed)
	 {
		 stepSpeed=speed;
	 }
	
	/**
	 * gets the creation probability's of all actors .
	 * @return String
	 */
	public static String getStatic()
	{
		return 
		"Fox chance: "+ FOX_CREATION_PROBABILITY + 
		", Rabbit chance: " + RABBIT_CREATION_PROBABILITY + 
		", Duck chance: " + DUCK_CREATION_PROBABILITY +
		", Hunter chance: " + HUNTER_CREATION_PROBABILITY +
		", Grass chance" + GRASS_CREATION_PROBABILITY;
	}
	
	/**
	 * sets the creation probability's of all actors.
	 * @param Fspawn
	 * @param Rspawn
	 * @param Dspawn
	 * @param Hspawn
	 * @param Gspawn
	 */
	public static void setStatic(double Fspawn, double Rspawn, double Dspawn, double Hspawn, double Gspawn)
	{
		FOX_CREATION_PROBABILITY = Fspawn; 
		RABBIT_CREATION_PROBABILITY  = Rspawn;
		DUCK_CREATION_PROBABILITY = Dspawn;
		HUNTER_CREATION_PROBABILITY = Hspawn;
		GRASS_CREATION_PROBABILITY = Gspawn;
	}
	
}

